//-----------------------------------------------------------------------
// <copyright file="MockStyleStubTest.cs" company="NMock2">
//
//   http://www.sourceforge.net/projects/NMock2
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
// </copyright>
// This is the easiest way to ignore StyleCop rules on this file, even if we shouldn't use this tag:
// <auto-generated />
//-----------------------------------------------------------------------

namespace NMock2
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using AcceptanceTests;
    using Internal;
    using NUnit.Framework;

    public interface IDependencyProvider
    {
        IDependency InterfaceDependency { get; }
        Dependency ClassDependency { get; }
        int Integer { get; }
        string String { get; }
        object GetAndSetProperty { get; set; }

        IDependency GetInterfaceDependency();
        Dependency GetClassDependency();

        int GetInteger();

        List<int> GetList();

        void NoReturnValue();

        int OverloadedMethod(int input);
        string OverloadedMethod(string input);
    }

    public abstract class DependencyProvider : IDependencyProvider
    {
        public abstract IDependency InterfaceDependency { get; }
        public abstract Dependency ClassDependency { get; }
        
        public abstract int Integer { get; }
        public abstract string String { get; }

        public abstract object GetAndSetProperty { get; set; }

        public abstract IDependency GetInterfaceDependency();
        public abstract Dependency GetClassDependency();

        public abstract int GetInteger();

        public abstract List<int> GetList();

        public abstract void NoReturnValue();

        public abstract int OverloadedMethod(int input);
        public abstract string OverloadedMethod(string input);

    }

    public interface IDependency
    {
        Dependency AnotherDependency { get; }
    }

    public abstract class Dependency
    {
        public abstract IDependency AnotherDependency { get; }
    }

    /// <summary>
    /// Generic base class to allow same tests for interfaces and classes and
    /// <see cref="NMock2.MockStyle.Stub"/> and <see cref="NMock2.MockStyle.Stub"/>.
    /// </summary>
    /// <typeparam name="TMock">The type of the mock.</typeparam>
    public abstract class MockStyleStubTest<TMock> where TMock : IDependencyProvider
    {
        /// <summary>
        /// Mock factory.
        /// </summary>
        private Mockery mockery;

        /// <summary>
        /// interface used in tests.
        /// </summary>
        private TMock dependencyProvider;

        /// <summary>
        /// Gets the dependency provider that is tested.
        /// </summary>
        /// <value>The dependency provider.</value>
        protected TMock DependencyProvider 
        {
            get { return this.dependencyProvider; }
        }

        /// <summary>
        /// Sets up the tests.
        /// </summary>
        [SetUp]
        public void SetUp()
        {
            this.mockery = new Mockery();

            this.dependencyProvider = this.mockery.NewMock<TMock>(MockStyle.Stub);
        }

        /// <summary>
        /// Stubs return newly created mocks with <see cref="NMock2.MockStyle.Stub"/> as results to method/properties that return interfaces.
        /// </summary>
        [Test]
        public void StubsReturnMocksForInterfaces()
        {
            Assert.IsNotNull(this.dependencyProvider.InterfaceDependency);
            Assert.IsNotNull(this.dependencyProvider.GetInterfaceDependency());
        }

        /// <summary>
        /// Stubs return mocks with <see cref="NMock2.MockStyle.Stub"/> as results to method/properties that return classes.
        /// </summary>
        [Test]
        public void StubsReturnMocksForClasses()
        {
            Assert.IsNotNull(this.dependencyProvider.ClassDependency);
            Assert.IsNotNull(this.dependencyProvider.GetClassDependency());
        }

        /// <summary>
        /// Stubs return default values for value types.
        /// </summary>
        [Test]
        public void StubsReturnDefaultValuesForValueTypes()
        {
            Assert.AreEqual(default(int), this.dependencyProvider.Integer);
            Assert.AreEqual(default(int), this.dependencyProvider.GetInteger());
        }

        /// <summary>
        /// Stubs return an empty string for string return values.
        /// </summary>
        [Test]
        public void StubsReturnStringEmptyForStrings()
        {
            Assert.IsEmpty(this.dependencyProvider.String);
        }

        /// <summary>
        /// Stubs swallow calls to void methods.
        /// </summary>
        [Test]
        public void StubsSwallowCallsToVoidMethods()
        {
            this.dependencyProvider.NoReturnValue();
        }

        /// <summary>
        /// The value returned by a stub can be overruled depending on the requested type.
        /// Missing.Value is used to not override the default behavior.
        /// </summary>
        [Test]
        public void ResolveType()
        {
            this.mockery.SetResolveTypeHandler(delegate(object mock, Type requestedType)
                                                   {
                                                       if (mock == (object)this.dependencyProvider && requestedType == typeof(int))
                                                       {
                                                           return 5;
                                                       }

                                                       return Missing.Value;
                                                   });

            Assert.AreEqual(5, this.dependencyProvider.GetInteger());
        }

        /// <summary>
        /// The <see cref="MockStyle"/> used for a returned mock can be overruled depending on the type of the
        /// return value and the stub that is called.
        /// </summary>
        [Test]
        [ExpectedException(typeof(ExpectationException))]
        public void ResolveMockStyleWhenMockStyleIsDefinedForType()
        {
            this.mockery.SetStubMockStyle<Dependency>(this.dependencyProvider, MockStyle.Default);
            
            Dependency dependency = this.dependencyProvider.ClassDependency;
            var anotherDependency = dependency.AnotherDependency;
        }

        /// <summary>
        /// The <see cref="MockStyle"/> used for a returned mock can be overruled depending on the type of the
        /// return value and the stub that is called.
        /// </summary>
        [Test]
        [ExpectedException(typeof(ExpectationException))]
        public void ResolveMockStyleWhenMockStyleIsDefinedForStub()
        {
            this.mockery.SetStubMockStyle(this.dependencyProvider, MockStyle.Default);

            Dependency dependency = this.dependencyProvider.ClassDependency;
            var anotherDependency = dependency.AnotherDependency;
        }

        /// <summary>
        /// Stubs returns empty objects for enumerables (lists, dictionaries, ...)
        /// </summary>
        [Test]
        public void StubReturnsEmptyObjectForEnumerables()
        {
            Assert.AreEqual(0, this.dependencyProvider.GetList().Count);
        }

        /// <summary>
        /// Makes sure that method overloads are correctly identified
        /// when stub behaviour is applied.
        /// </summary>
        [Test]
        public void StubsRespectOverloadedMethods()
        {
            Assert.AreEqual(0, this.dependencyProvider.OverloadedMethod(1));
            Assert.AreEqual(string.Empty, this.dependencyProvider.OverloadedMethod("A"));
        }
    
        /// <summary>
        /// The name of auto stubs reflect the path they were accessed/created.
        /// </summary>
        [Test]
        public void Naming()
        {
            IDependency dependency = this.dependencyProvider.InterfaceDependency;

            Assert.AreEqual("dependencyProvider.InterfaceDependency", ((IMockObject)dependency).MockName, "wrong name of direct dependency.");

            Assert.AreEqual(
                "dependencyProvider.InterfaceDependency.AnotherDependency.AnotherDependency.AnotherDependency",
                ((IMockObject)dependency.AnotherDependency.AnotherDependency.AnotherDependency).MockName,
                "wrong name of indirect dependency.");
        }

        /// <summary>
        /// Calls on the same property/method return the same value.
        /// </summary>
        [Test]
        public void SameValueIsReturned()
        {
            Assert.AreSame(this.DependencyProvider.ClassDependency, this.DependencyProvider.ClassDependency);
            Assert.AreSame(this.DependencyProvider.GetInterfaceDependency(), this.DependencyProvider.GetInterfaceDependency());
            Assert.AreSame(this.DependencyProvider.GetList(), this.DependencyProvider.GetList());
        }

        /// <summary>
        /// Stubs remember the values set on properties and the value can be accessed with the getter.
        /// </summary>
        [Test]
        public void SetAndGetProperty()
        {
            object value = "hello";

            this.DependencyProvider.GetAndSetProperty = value;
            Assert.AreSame(value, this.DependencyProvider.GetAndSetProperty, "wrong value returned.");

            this.DependencyProvider.GetAndSetProperty = 3;
            Assert.AreEqual(3, this.DependencyProvider.GetAndSetProperty, "wrong value on second set.");
        }

        [Test]
        [ExpectedException(typeof(ExpectationException))]
        public void TooManyInvocationsToAnExpectedCall()
        {
            Expect.Never.On(this.dependencyProvider).GetProperty("String");

            var value = this.dependencyProvider.String;
        }

        [Test]
        public void ChainedExpectations()
        {
            const string First = "hello";
            const string Second = "world";

            Expect.Once.On(this.dependencyProvider).GetProperty("String").Will(Return.Value(First));
            Expect.Once.On(this.dependencyProvider).GetProperty("String").Will(Return.Value(Second));

            var first = this.dependencyProvider.String;
            var second = this.dependencyProvider.String;

            Assert.AreEqual(First, first);
            Assert.AreEqual(Second, second);
        }
    }

    /// <summary>
    /// Tests the <see cref="NMock2.MockStyle.Stub"/> option for interfaces.
    /// </summary>
    [TestFixture, Class]
    public class InterfaceMockStyleRecursiveStubTest : MockStyleStubTest<IDependencyProvider>
    {
    }

    /// <summary>
    /// Tests the <see cref="NMock2.MockStyle.Stub"/> option for classes.
    /// </summary>
    [TestFixture, Class]
    public class ClassMockStyleRecursiveStubTest : MockStyleStubTest<DependencyProvider>
    {
    }
}